package com.boost.tjvm.agent;

import java.lang.instrument.Instrumentation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;

/**
 * 由于要用到java.lang.instrument.Instrumentation，
 * 本代码须要打成jar包以javaagent运行，manifest.mf文件内容以下
   Manifest-Version: 1.0
   Premain-Class: com.teasp.mem.SizeOfAgent
   Boot-Class-Path: 
   Can-Redefine-Classes: false
 * 运行方式：打包成sizeof.jar后，
 * 执行 java -javaagent:import-checker-1.0-SNAPSHOT.jar com.tc.checker.cache.SizeOfAgent
 */
public class SizeOfAgent
{
    private static Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
    private static Instrumentation inst;
    
    /** initializes agent  在main 方法之前执行*/
    public static void premain(String agentArgs, Instrumentation instP) 
    {
        inst = instP;
    }


    /**
     * Returns object size without member sub-objects.
     * @param o object to get size of
     * @return object size
     */
    public static long sizeOf(Object o) 
    {
        if(inst == null) 
        {
            throw new IllegalStateException("Can not access instrumentation environment.\n" +
                            "Please check if jar file containing SizeOfAgent class is \n" +
                            "specified in the java's \"-javaagent\" command line argument.");
        }
            return inst.getObjectSize(o);
    }
                  
    /**
     * Calculates full size of object iterating over
     * its hierarchy graph.
     * @param obj object to calculate size of
     * @return object size
     *
     * 单位字节(是指Byte)
     *
     * 在计算机科学中，bit（比特）是表示信息的最小单位，叫做二进制位，一般用0和1表示。
     * Byte叫做字节，由8个位（8bit）组成一个字节(1Byte)，用于表示计算机中的一个字符。
     * 通常都是以字节为单位（即大M 大B） ， 只有特指的时候才说 小M 和小B (以比特为单位的)
     */
    public static long fullSizeOf(Object obj) 
    {
//        Map<Object, Object> visited = new IdentityHashMap<Object, Object>();
        Stack<Object> stack = new Stack<Object>();
          
        long result = internalSizeOf(obj, stack/*, visited*/);
        while (!stack.isEmpty()) 
        {
            result += internalSizeOf(stack.pop(), stack/*, visited*/);
        }
//        visited.clear();
        return result;
    }

    public static long fullSizeOf_M(Object obj){


        return fullSizeOf_K(obj)>>10;
    }


    public static long fullSizeOf_K(Object obj){


        return fullSizeOf(obj)>>10;
    }

    public static long fullSizeOf_G(Object obj){


        return fullSizeOf_M(obj)>>10;
    }


    private static boolean skipObject(Object obj/*, Map<Object, Object> visited*/) 
    {
//        if (obj instanceof String) {
//            // skip interned string
//            if (obj == ((String) obj).intern()) {
//                return true;
//            }
//        }
        return (obj == null) || visited.containsKey(obj);
    }
  
    @SuppressWarnings("rawtypes")
    private static long internalSizeOf(Object obj, Stack<Object> stack/*, Map<Object, Object> visited*/) 
    {
        if (skipObject(obj/*, visited*/))
        {
            return 0;
        }
        visited.put(obj, null);
                      
        long result = 0;
        // get size of object + primitive variables + member pointers 
        result += SizeOfAgent.sizeOf(obj);
                  
        // process all array elements
        Class clazz = obj.getClass();
        if (clazz.isArray()) 
        {
            if(clazz.getName().length() != 2) 
            {// skip primitive type array
                int length =  Array.getLength(obj);
                for (int i = 0; i < length; i++) 
                {
                    stack.add(Array.get(obj, i));
                } 
            }       
            return result;
        }
                  
        // process all fields of the object
        while (clazz != null) 
        {
            Field[] fields = clazz.getDeclaredFields();
            for (int i = 0; i < fields.length; i++) 
            {
                if (!Modifier.isStatic(fields[i].getModifiers()))
                {
                    if (fields[i].getType().isPrimitive()) 
                    {
                        continue; // skip primitive fields
                    } 
                    else 
                    {
                        fields[i].setAccessible(true);
                        try 
                        {
                            // objects to be estimated are put to stack
                            Object objectToAdd = fields[i].get(obj);
                            if (objectToAdd != null) 
                            {                        
                                stack.add(objectToAdd);
                            }
                        } 
                        catch (IllegalAccessException ex) 
                        { 
                            assert false; 
                        }
                    }
                }
            }
            clazz = clazz.getSuperclass();
        }
        return result;
    }

    public static void main(String[] args) throws Exception
    {


        System.out.println(UUID.randomUUID().toString());

        System.out.println("int : " + fullSizeOf(1));
        System.out.println("int : " + fullSizeOf("1"));

        System.out.println("int : " + fullSizeOf(new Long(1)));
        ArrayList arrayList = new ArrayList(10);
        System.out.println("arrayList: " + fullSizeOf(arrayList));

        System.out.println("null object: " + fullSizeOf(new Object()));
        HashMap<String,String> map=new HashMap<>();
        for (int i=0;i<4000000;i++){
            map.put(UUID.randomUUID().toString().substring(14), UUID.randomUUID().toString().substring(14));
        }
        System.out.println("map object: " + fullSizeOf_M(map));

    }
}